<?php
declare (strict_types=1);

/**
 * 应用公共函数库文件
 */

use cores\exception\BaseException;
use cores\exception\DebugException;
use cores\library\Version;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use think\exception\HttpResponseException;
use think\facade\Config;
use think\facade\Env;
use think\facade\Log;
use think\facade\Request;
use think\Response;

/**
 * 打印调试函数 html
 * @param $content
 * @param bool $export
 */
function pre($content, bool $export = false)
{
    $output = $export ? var_export($content, true) : print_r($content, true);
    echo "<pre>{$output}</pre>";
    app_end();
}

/**
 * 打印调试函数 json
 * @param $content
 * @param bool $export
 * @throws DebugException
 */
function pree($content, bool $export = false)
{
    $output = $export ? var_export($content, true) : $content;
    throw new DebugException([], $output);
}

/**
 * 打印调试函数 输出在终端
 * 只有debug模式下输出
 * @param $content
 * @return void
 */
function tre($content)
{
    if (is_debug()) {
        $output = print_r($content, true);
        echo "$output\n";
    }
}

/**
 * 输出错误信息
 * @param string $message 报错信息
 * @param int|null $status 状态码,默认为配置文件status.error
 * @param array $data 附加数据
 * @throws BaseException
 */
function throwError(string $message, ?int $status = null, array $data = [])
{
    is_null($status) && $status = config('status.error');
    throw new BaseException(['status' => $status, 'message' => $message, 'data' => $data]);
}

/**
 * 下划线转驼峰
 * @param string $uncamelized_words
 * @param string $separator
 * @return string
 */
function camelize(string $uncamelized_words, string $separator = '_'): string
{
    $uncamelized_words = $separator . str_replace($separator, " ", strtolower($uncamelized_words));
    return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), $separator);
}

/**
 * 驼峰转下划线
 * @param string $camelCaps
 * @param string $separator
 * @return string
 */
function uncamelize(string $camelCaps, string $separator = '_'): string
{
    return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . $separator . "$2", $camelCaps));
}

/**
 * 生成密码hash值
 * @param string $password
 * @return string
 */
function encryption_hash(string $password): string
{
    return password_hash($password, PASSWORD_DEFAULT);
}

/**
 * 获取当前域名及根路径
 * @return string
 */
function base_url(): string
{
    static $baseUrl = '';
    if (empty($baseUrl)) {
        $request = Request::instance();
        // url协议，设置强制https或自动获取
        $scheme = Config::get('route')['url_force_https'] ? 'https' : $request->scheme();
        // url子目录
        $rootUrl = root_url();
        // 拼接完整url
        $baseUrl = "{$scheme}://" . $request->host() . $rootUrl;
    }
    return $baseUrl;
}

/**
 * 获取当前url的子目录路径
 * @return string
 */
function root_url(): string
{
    static $rootUrl = '';
    if (empty($rootUrl)) {
        $request = Request::instance();
        $subUrl = str_replace('\\', '/', dirname($request->baseFile()));
        $rootUrl = $subUrl . ($subUrl === '/' ? '' : '/');
    }
    return $rootUrl;
}

/**
 * 获取当前uploads目录访问地址
 * @return string
 */
function uploads_url(): string
{
    return base_url() . 'uploads/';
}

/**
 * 获取当前temp目录访问地址
 * @return string
 */
function temp_url(): string
{
    return base_url() . 'temp/';
}

/**
 * 获取当前的应用名称
 * @return mixed
 */
function app_name()
{
    return app('http')->getName();
}

/**
 * 获取web根目录
 * @return string
 */
function web_path(): string
{
    static $webPath = '';
    if (empty($webPath)) {
        $request = Request::instance();
        $webPath = dirname($request->server('SCRIPT_FILENAME')) . DIRECTORY_SEPARATOR;
    }
    return $webPath;
}

/**
 * 获取data目录路径
 * @return string
 */
function data_path(): string
{
    return config('filesystem.disks.data.root') . DIRECTORY_SEPARATOR;
}

/**
 * 获取runtime根目录路径
 * @return string
 */
function runtime_root_path(): string
{
//    return dirname(runtime_path()) . DIRECTORY_SEPARATOR;
    return root_path() . 'runtime' . DIRECTORY_SEPARATOR;
}

/**
 * 写入日志 (使用tp自带驱动记录到runtime目录中)
 * @param mixed $value
 * @param string $type
 */
function log_record($value, string $type = 'info')
{
    $content = is_string($value) ? $value : print_r($value, true);
    Log::record($content, $type);
}

/**
 * 多维数组合并
 * @param $array1
 * @param $array2
 * @return array
 */
function array_merge_multiple($array1, $array2): array
{
    $merge = $array1 + $array2;
    $data = [];
    foreach ($merge as $key => $val) {
        if (
            isset($array1[$key])
            && is_array($array1[$key])
            && isset($array2[$key])
            && is_array($array2[$key])
        ) {
            $data[$key] = is_assoc($array1[$key]) ? array_merge_multiple($array1[$key], $array2[$key]) : $array2[$key];
        } else {
            $data[$key] = $array2[$key] ?? $array1[$key];
        }
    }
    return $data;
}

/**
 * 判断是否为自定义索引数组
 * @param array $array
 * @return bool
 */
function is_assoc(array $array): bool
{
    if (empty($array)) return false;
    return array_keys($array) !== range(0, count($array) - 1);
}

/**
 * 二维数组排序
 * @param $arr
 * @param $keys
 * @param bool $desc
 * @return array
 */
function array_sort($arr, $keys, bool $desc): array
{
    $key_value = $new_array = array();
    foreach ($arr as $k => $v) {
        $key_value[$k] = $v[$keys];
    }
    if ($desc) {
        arsort($key_value);
    } else {
        asort($key_value);
    }
    reset($key_value);
    foreach ($key_value as $k => $v) {
        $new_array[$k] = $arr[$k];
    }
    return $new_array;
}

/**
 * 隐藏敏感字符
 * @param $value
 * @return string
 */
function substr_cut($value): string
{
    $strlen = mb_strlen($value, 'utf-8');
    if ($strlen <= 1) return $value;
    $firstStr = mb_substr($value, 0, 1, 'utf-8');
    $lastStr = mb_substr($value, -1, 1, 'utf-8');
    return $strlen == 2 ? $firstStr . str_repeat('*', $strlen - 1) : $firstStr . str_repeat("*", $strlen - 2) . $lastStr;
}

/**
 * 获取当前系统版本号
 * @return string
 * @throws BaseException
 */
function get_version(): string
{
    return Version::getVersion();
}

/**
 * 获取全局唯一标识符
 * @param bool $trim
 * @return string
 */
function get_guid_v4(bool $trim = true): string
{
    // Windows
    if (function_exists('com_create_guid') === true) {
        $charid = com_create_guid();
        return $trim ? trim($charid, '{}') : $charid;
    }
    // OSX/Linux
    if (function_exists('openssl_random_pseudo_bytes') === true) {
        $data = openssl_random_pseudo_bytes(16);
        $data[6] = chr(ord($data[6]) & 0x0f | 0x40);    // set version to 0100
        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);    // set bits 6-7 to 10
        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
    }
    // Fallback (PHP 4.2+)
    mt_srand(intval((double)microtime() * 10000));
    $charid = strtolower(md5(uniqid((string)rand(), true)));
    $hyphen = chr(45);                  // "-"
    $lbrace = $trim ? "" : chr(123);    // "{"
    $rbrace = $trim ? "" : chr(125);    // "}"
    return $lbrace .
        substr($charid, 0, 8) . $hyphen .
        substr($charid, 8, 4) . $hyphen .
        substr($charid, 12, 4) . $hyphen .
        substr($charid, 16, 4) . $hyphen .
        substr($charid, 20, 12) .
        $rbrace;
}

/**
 * 时间戳转换日期
 * @param int|string $timeStamp 时间戳
 * @param bool $withTime 是否关联时间
 * @return false|string
 */
function format_time($timeStamp, bool $withTime = true)
{
    $format = 'Y-m-d';
    $withTime && $format .= ' H:i:s';
    return $timeStamp ? date($format, $timeStamp) : '';
}

/**
 * 左侧填充0
 * @param string $value
 * @param int $padLength
 * @return string
 */
function pad_left(string $value, int $padLength = 2): string
{
    return str_pad($value, $padLength, '0', STR_PAD_LEFT);
}

/**
 * 重写trim方法 (解决int类型过滤后后变为string类型)
 * @param $str
 * @return string|void
 */
function my_trim($str)
{
    return is_string($str) ? trim($str) : $str;
}

/**
 * 重写htmlspecialchars方法 (解决int类型过滤后后变为string类型)
 * @param $string
 * @return string|void
 */
function my_htmlspecialchars($string)
{
    return is_string($string) ? htmlspecialchars($string, ENT_COMPAT) : $string;
}

/**
 * 过滤emoji表情
 * @param $text
 * @return null|string|string[]
 */
function filter_emoji($text)
{
    if (!is_string($text)) {
        return $text;
    }
    // 此处的preg_replace用于过滤emoji表情
    // 如需支持emoji表情, 需将mysql的编码改为utf8mb4
    return preg_replace('/[\xf0-\xf7].{3}/', '', $text);
}

/**
 * 根据指定长度截取字符串
 * @param $str
 * @param int $length
 * @return bool|string
 */
function str_substr($str, int $length = 30)
{
    if (strlen($str) > $length) {
        $str = mb_substr($str, 0, $length);
    }
    return $str;
}

/**
 * 结束执行
 */
function app_end()
{
    if (\request()->isCli()) {
        exit(PHP_EOL);
    }
    throw new HttpResponseException(Response::create());
}

/**
 * 当前是否为调试模式
 * @return bool
 */
function is_debug(): bool
{
    return (bool)Env::instance()->get('APP_DEBUG');
}

/**
 * 文本左斜杠转换为右斜杠
 * @param string $string
 * @return string
 */
function convert_left_slash(string $string): string
{
    return str_replace('\\', '/', $string);
}

/**
 * 隐藏手机号中间四位 13012345678 -> 130****5678
 * @param string $mobile 手机号
 * @return string
 */
function hide_mobile(string $mobile): string
{
    return substr_replace($mobile, '****', 3, 4);
}

/**
 * 获取当前登录的平台ID
 * @return int $storeId
 */
function getStoreId(): int
{
    return 10001;
}

/**
 * excel表格导出
 * @param string $fileName 文件名称 $name='测试导出';
 * @param array $headArr 表头名称 $header=['表头A','表头B'];
 * @param array $data 要导出的数据 $data=[['测试','测试'],['测试','测试']]
 * @param bool $auto 是否开启根据表头自适应宽度 默认开启
 * @author php
 */
function excelExport($fileName = '', $headArr = [], $data = [], $auto = true)
{

    $fileName .= ".xlsx";
    $objPHPExcel = new Spreadsheet();
    $objPHPExcel->getProperties();
    $key = ord("A"); // 设置表头
    $key2 = ord("@"); //	超过26列会报错的解决方案

    // 居中
    $objPHPExcel->getDefaultStyle()->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
    $objPHPExcel->getDefaultStyle()->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);

    // 设置表头
    foreach ($headArr as $v) {
        // 超过26列会报错的解决方案
        if ($key > ord("Z")) {
            $key2 += 1;
            $key = ord("A");
            $colum = chr($key2) . chr($key); //超过26个字母时才会启用
        } else {
            if ($key2 >= ord("A")) {
                $colum = chr($key2) . chr($key);
            } else {
                $colum = chr($key);
            }
        }
        // 写入表头
        $objPHPExcel->setActiveSheetIndex(0)->setCellValue($colum . '1', $v);
        // 自适应宽度
        if ($auto) {
            // $len = strlen(iconv('utf-8','gb2312',$v));//会报错
            $len = strlen(iconv('utf-8', 'gbk', $v));
            $objPHPExcel->getActiveSheet()->getColumnDimension($colum)->setWidth($len + 5);
        }
        $key += 1;
    }

    $column = 2;
    $objActSheet = $objPHPExcel->getActiveSheet();
    // 写入行数据
    foreach ($data as $key => $rows) {
        $span = ord("A");
        $span2 = ord("@");
        // 按列写入
        foreach ($rows as $keyName => $value) {
            // 超过26列会报错的解决方案
            if ($span > ord("Z")) {
                $span2 += 1;
                $span = ord("A");
                $tmpSpan = chr($span2) . chr($span); //超过26个字母时才会启用
            } else {
                if ($span2 >= ord("A")) {
                    $tmpSpan = chr($span2) . chr($span);
                } else {
                    $tmpSpan = chr($span);
                }
            }


            $index = $tmpSpan . $column;
            if ($tmpSpan == "B" || $tmpSpan == "E") {
                $objPHPExcel->getActiveSheet()->setCellValueExplicit(
                    $index,
                    $value,
                    DataType::TYPE_STRING
                );
            } else {
                $objActSheet->setCellValue($index, $value,);
            }

            // 写入数据
//            $objActSheet->setCellValue($tmpSpan . $column, $value,);
            $span++;
        }
        $column++;
    }

    // 自动加边框
    $styleThinBlackBorderOutline = array(
        'borders' => array(
            'allborders' => array( //设置全部边框
                'style' => Border::BORDER_THIN //粗的是thick
            ),

        ),
    );

    $objPHPExcel->getActiveSheet()->getStyle('A1:' . $colum . --$column)->applyFromArray($styleThinBlackBorderOutline);
    // 重命名表
    // $fileName = iconv("utf-8", "gb2312", $fileName);
    $fileName = iconv("utf-8", "gbk", $fileName);
    // 设置活动单指数到第一个表,所以Excel打开这是第一个表
    $objPHPExcel->setActiveSheetIndex(0);
    header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
    header("Content-Disposition: attachment;filename=$fileName");
    header('Cache-Control: max-age=0');
    // $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($objPHPExcel, 'Xlsx');
    $writer = new Xlsx($objPHPExcel);
    $writer->save('php://output'); // 文件通过浏览器下载
    exit();
}

function get_age($idNumber)
{
    $birthdayInfo = substr($idNumber, 6, 8);
    $a = (substr_replace($birthdayInfo, "-", 4, 0));
    $birthdayTimeStamp = strtotime(substr_replace($a, "-", 7, 0));
    return date("Y") - date("Y", $birthdayTimeStamp) - (date("md", $birthdayTimeStamp) > date("md"));
}

function sendSMS($mobile)
{
    $code = rand(100000, 999999);
    $postData = [
        'sp_id' => '624547',
        'password' => md5('c5c2ce27'),
        'mobile' => $mobile,
        'content' => "【科懿科技】欢迎使用测评系统，您的手机认证验证码为：{$code}，请勿泄露给他人！",
    ];
    $curl = curl_init();

    curl_setopt_array($curl, [
        CURLOPT_PORT => "9511",
        CURLOPT_URL => "http://121.36.61.51:9511/api/send-sms-single",
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => "",
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => "POST",
        CURLOPT_POSTFIELDS => json_encode($postData, 320),
        CURLOPT_HTTPHEADER => [
            "content-type: application/json"
        ],
    ]);
    $response = curl_exec($curl);
    $err = curl_error($curl);
    curl_close($curl);
    if ($err) {
        throwError($err);
    } else {
        $res = json_decode($response, true);
        if (!empty($res['code'])) {
            throwError($res['msg'] ?? '发送失败');
        }
        return $code;
    }
}

/**
 * 日期转换时间戳(不保留时间)
 * 例如: 2020-04-01 08:15:08 => 1585670400
 * @param string $date
 * @return false|int
 */
function str2time_date(string $date)
{
    return strtotime(date('Y-m-d', strtotime($date)));
}

/**
 * 格式化起止时间(为了兼容前端RangePicker组件)
 * 2020-04-01T08:15:08.891Z => 1585670400
 * @param array $times
 * @return array
 */
function between_time($times)
{
    if (is_array($times)) {
        foreach ($times as &$time) {
            $time = trim($time, '&quot;');
            $time = str2time_date($time);
        }
        return ['start_time' => current($times), 'end_time' => next($times)];
    } else {
        $times = explode('/', $times);
        foreach ($times as &$time) {
            $time = trim($time, '&quot;');
            $time = str2time_date($time);
        }
        return ['start_time' => current($times), 'end_time' => next($times)];
    }
}

function between_time2($times)
{
    if (is_array($times)) {
        foreach ($times as &$time) {
            $time = trim($time, '&quot;');
            $time = strtotime(date('Y-m-d H:i:s', strtotime($time)));
        }
        return ['start_time' => current($times), 'end_time' => next($times)];
    } else {
        $times = explode('/', $times);
        foreach ($times as &$time) {
            $time = trim($time, '&quot;');
            $time = strtotime(date('Y-m-d H:i:s', strtotime($time)));
        }
        return ['start_time' => current($times), 'end_time' => next($times)];
    }
}

//检查是否是身份证号
function isIdCard($id)
{
    $id = strtoupper($id);
    $regx = "/(^\d{15}$)|(^\d{17}([0-9]|X)$)/";
    $arr_split = array();
    if (!preg_match($regx, $id)) {
        return false;
    }
    if (15 == strlen($id)) //检查15位
    {
        $regx = "/^(\d{6})+(\d{2})+(\d{2})+(\d{2})+(\d{3})$/";

        @preg_match($regx, $id, $arr_split);
        //检查生日日期是否正确
        $dtm_birth = "19" . $arr_split[2] . '/' . $arr_split[3] . '/' . $arr_split[4];
        if (!strtotime($dtm_birth)) {
            return false;
        } else {
            return true;
        }
    } else //检查18位
    {
        $regx = "/^(\d{6})+(\d{4})+(\d{2})+(\d{2})+(\d{3})([0-9]|X)$/";
        @preg_match($regx, $id, $arr_split);
        $dtm_birth = $arr_split[2] . '/' . $arr_split[3] . '/' . $arr_split[4];
        if (!strtotime($dtm_birth)) //检查生日日期是否正确
        {
            return false;
        } else {
            //检验18位身份证的校验码是否正确。
            //校验位按照ISO 7064:1983.MOD 11-2的规定生成，X可以认为是数字10。
            $arr_int = array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2);
            $arr_ch = array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2');
            $sign = 0;
            for ($i = 0; $i < 17; $i++) {
                $b = (int)$id[$i];
                $w = $arr_int[$i];
                $sign += $b * $w;
            }
            $n = $sign % 11;
            $val_num = $arr_ch[$n];
            if ($val_num != substr($id, 17, 1)) {
                return false;
            } else {
                return true;
            }
        }
    }
}

function get_age2($idNumber)
{
    $birthdayInfo = substr($idNumber, 6, 8);
    $a = (substr_replace($birthdayInfo, "-", 4, 0));
    $birthdayTimeStamp = strtotime(substr_replace($a, "-", 7, 0));
    return date("Y") - date("Y", $birthdayTimeStamp) - (date("md", $birthdayTimeStamp) > date("md"));
}

/**
 * 根据身份证号码获取性别（包含基本验证）
 * @param string $idCard 身份证号码
 * @return string 性别：男/女/未知
 */
function getGenderFromIdCard($idCard) {
    // 去除所有非数字和Xx字符
    $idCard = preg_replace('/[^0-9Xx]/', '', $idCard);

    // 验证身份证号码长度
    $length = strlen($idCard);
    if ($length != 15 && $length != 18) {
        return '未知';
    }

    // 验证18位身份证最后一位
    if ($length == 18) {
        // 前17位必须都是数字
        if (!ctype_digit(substr($idCard, 0, 17))) {
            return '未知';
        }

        // 最后一位可以是数字或X/x
        $lastChar = strtoupper(substr($idCard, 17, 1));
        if (!ctype_digit($lastChar) && $lastChar != 'X') {
            return '未知';
        }
    } else {
        // 15位身份证必须全部是数字
        if (!ctype_digit($idCard)) {
            return '未知';
        }
    }

    // 获取性别位
    $genderDigit = ($length == 15) ? substr($idCard, 14, 1) : substr($idCard, 16, 1);

    // 判断性别
    return ($genderDigit % 2 == 1) ? '男' : '女';
}
